package com.me.common.util.security;

import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

import java.io.IOException;
import java.io.StringWriter;
import java.security.*;
import java.util.Base64;

/**
 * 生成证书请求对象（CSR）
 */
public class GenerateCSR {

    private static PublicKey publicKey = null;

    private static PrivateKey privateKey = null;

    private static KeyPairGenerator keyGen = null;

    private static GenerateCSR gcsr = null;

    private GenerateCSR() {
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        keyGen.initialize(2048, new SecureRandom());
        KeyPair keypair = keyGen.generateKeyPair();
        publicKey = keypair.getPublic();
        privateKey = keypair.getPrivate();
    }

    public static GenerateCSR getInstance() {
        if (gcsr == null) {
            gcsr = new GenerateCSR();
        }
        return gcsr;
    }

    public static void main(String[] args) throws Exception {
        GenerateCSR gcsr = GenerateCSR.getInstance();
        System.out.println("Public Key:\n" + gcsr.getPublicKey().toString());

        // 打印私钥，注意：请务必保存您的私钥
        System.out.println("\nPrivate Key:\n" + gcsr.getPrivateKey().toString());
        System.out.println("\nPKCS8 Private Key:\n" + RSAUtil.priKey2Pkcs8Pem(privateKey, true));

        String csr = gcsr.genCSR("journaldev.com", "Java", "JournalDev", "Cupertino", "California", "USA");
        System.out.println("\nCSR Request Generated!!");
        System.out.println(csr);
    }

    /**
     * 产生 证书签发请求（CSR）
     *
     * @param CN 通用名，最好将其与您的组织联系起来
     * @param OU 组织单位
     * @param O  组织名
     * @param L  地址，位置
     * @param S  省市
     * @param C  国家
     */
    public String genCSR(String CN, String OU, String O, String L, String S, String C) {
        try {
            Security.addProvider(new BouncyCastleProvider());
            // 使用私钥和 SHA256WithRSA/SM3withSM2 算法创建签名者对象
            ContentSigner signer = new JcaContentSignerBuilder(SignatureUtil.SHA256_WITH_RSA_SIGNATURE)
                    .setProvider("BC").build(privateKey);

            // 按需添加证书主题项，
            // 有些 CSR 不需要我们在主题项中添加各字段，如 `C=CN, OU=3303..., L=杭州, S=浙江`，
            // 而是通过额外参数提交，故这里我只简单地指定了国家码
            X500Name subjectDn = new X500Name("CN=" + CN);

            // 创建 CSR
            // PKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(subjectDn, publicKey);
            PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(subjectDn, SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
            PKCS10CertificationRequest csr = builder.build(signer);
            CertificationRequest req = csr.toASN1Structure();

            // 以 Base64 字符串形式返回 CSR
            String csrBase64 = Base64.getEncoder().encodeToString(req.getEncoded());

            // 打印 OpenSSL PEM 格式文件字符串
            String pkcs10 = csr2PKCS10Pem(csr);
            System.out.println("\n" + pkcs10);

            return csrBase64;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * CSR对象转 PEM格式的 PKCS10
     */
    private static String csr2PKCS10Pem(PKCS10CertificationRequest csr) throws IOException {
        PemObject pem = new PemObject("CERTIFICATE REQUEST", csr.getEncoded());
        StringWriter str = new StringWriter();
        PemWriter pemWriter = new PemWriter(str);
        pemWriter.writeObject(pem);
        pemWriter.close();
        str.close();
        return str.toString();
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

}
